//
//  Shares.m
//  SolarSeek
//
//  Created by Erick Fejta on 05/01/17.
//  Copyright 2005 Erick Fejta. All rights reserved.
//

#import "Shares.h"
#import "Peers.h"
#import "Message.h"

#import "SolarSeekEngine.h"
#import "PeersController.h"

//fejta: TODO: make some of the attribute keys global constants.

@implementation Share
- (NSString *)filenameSharedUnix { 
	NSString *fs = [self filenameShared];
	return [[fs componentsSeparatedByString:@"\\"] componentsJoinedByString:@"/"];
}

- (id)initWithCoder:(NSCoder *)decoder {
	return self;
}

- (void)encodeWithCoder:(NSCoder *)coder {
}

@end

@implementation ShareLocal

- (id)initWithFile:(NSString *)file privilege:(unsigned)floor {
	self = [super init];
	if(self) {
		_filenameLocal = [[file stringByStandardizingPath] retain];
		_floor = floor;
	}
	return self;
}

- (id)initWithCoder:(NSCoder *)decoder {
	if([decoder allowsKeyedCoding]) {
		_filenameLocal = [[decoder decodeObjectForKey:@"filename"] retain];
		_floor = [decoder decodeInt32ForKey:@"share level"];
	}
	else {
		[NSException raise:NSInvalidArchiveOperationException format:@"Only supports NSKeyedArchiver coders"];
	}
	return self;
}

- (void)encodeWithCoder:(NSCoder *)decoder {
	if([decoder allowsKeyedCoding]) {
		[decoder encodeObject:_filenameLocal forKey:@"filename"];
		[decoder encodeInt32:_floor forKey:@"share level"];
	}
	else {
		[NSException raise:NSInvalidArchiveOperationException format:@"Only supports NSKeyedArchiver coders"];
	}
}


- (void)dealloc {
	[_filenameLocal release];
	[super dealloc];
}

- (BOOL)isEqual:(id)obj {
	return ([obj isKindOfClass:[ShareLocal class]] && [[self filenameLocal] isEqual:[obj filenameLocal]]);
}

- (unsigned)hash {
	return [_filenameLocal hash]^NSNotFound;
}

- (NSComparisonResult)compare:(ShareLocal *)other {
	return [_filenameLocal compare:[other filenameLocal]];
}

- (NSString *)description {
	return [self filenameShared];
}

#pragma mark KVC/O

- (NSImage *)icon { 
	return [[NSWorkspace sharedWorkspace] iconForFile:[[self filenameLocal] stringByStandardizingPath]];
}

- (unsigned long long)size {
	return [[[self attributes] objectForKey:NSFileSize] unsignedLongLongValue];
}

- (NSDictionary *)attributes {
	return [[NSFileManager defaultManager] fileAttributesAtPath:_filenameLocal traverseLink:YES];
}

- (NSString *)filenameSharedUnconverted { return [self filenameLocal]; }

- (NSString *)filenameShared { return [ShareLocal convertFilenameToStyleShared:[self filenameSharedUnconverted]]; }

- (unsigned)floorPrivileges { return _floor; }

+ (NSString *)convertFilenameToStyleShared:(NSString *)filenameLocal {
	NSArray *a = [filenameLocal pathComponents];
	if([[a objectAtIndex:0] isEqual:@"/"])
		a = [[NSArray arrayWithObject:@"SolarSeek:"] arrayByAddingObjectsFromArray:[a subarrayWithRange:NSMakeRange(1,[a count]-1)]];
	return [a componentsJoinedByString:@"\\"];
}

+ (NSString *)convertFilenameToStyleUnix:(NSString *)filenameShared {
	NSArray *a = [filenameShared componentsSeparatedByString:@"\\"];
	if([[a objectAtIndex:0] isEqual:@"SolarSeek:"]) {
		a = [[NSArray arrayWithObject:@""] arrayByAddingObjectsFromArray:[a subarrayWithRange:NSMakeRange(1,[a count]-1)]];
	}
	return [a componentsJoinedByString:@"/"];
}

- (NSString *)filenameLocal { return _filenameLocal; }

@end

#pragma mark -


@implementation SharePeer

- (id)init { 
	[self release];
	return nil;
}

- (id)initWithPeer:(Peer *)peer filenameData:(NSData *)filenameData andAttributes:(NSDictionary *)attributes {
	self = [super init];
	if(self) {
		_peer = [peer retain];
		_filenameData = [filenameData retain];
		_filenameShared = nil;
		_attributes = [attributes retain];
		_icon = nil;
	}
	return self;
}

- (id)initWithCoder:(NSCoder *)decoder {
	if([decoder allowsKeyedCoding]) {
		NSString *name = [decoder decodeObjectForKey:@"peer name"];
		NSAssert(name,@"The share must have a \"peer name\" key.");
		_peer = [[[NSApp engine] peerNamed:name] retain];
		_filenameShared = nil;
		_filenameData = [[decoder decodeObjectForKey:@"filename"] retain];
		_attributes = [[decoder decodeObjectForKey:@"attributes"] retain];
		_icon = nil;
	}
	else {
		[NSException raise:NSInvalidArchiveOperationException format:@"Only supports NSKeyedArchiver coders"];
	}
	return self;
}


- (void)encodeWithCoder:(NSCoder *)decoder {
	if([decoder allowsKeyedCoding]) {
		[decoder encodeObject:[_peer name] forKey:@"peer name"];
		[decoder encodeObject:_filenameData forKey:@"filename"];
		[decoder encodeObject:_attributes forKey:@"attributes"];
	}
	else {
		[NSException raise:NSInvalidArchiveOperationException format:@"Only supports NSKeyedArchiver coders"];
	}
}

- (void)dealloc {
	[_icon release];
	[_filenameData release];
	[_filenameShared release];
	[_attributes release];
	[super dealloc];
}

- (id)valueForUndefinedKey:(NSString *)key {
	return [_attributes valueForKey:key];
}

- (BOOL)isEqual:(id)other {
	BOOL a = [other isKindOfClass:[SharePeer class]];
	if(a) {
		BOOL b = [[self peer] isEqual:[other peer]];
		if(b) {
			unsigned len_my = [_filenameData length];
			unsigned len_o = [[other filenameData] length];
			if(len_my == len_o) {
				char *c_my = (char *)[_filenameData bytes];
				char *c_o = (char *)[[other filenameData] bytes];
				char *d_my = c_my+len_my;
				char *d_o = c_o + len_o;
				while(--d_my >= c_my) {
					--d_o;
					if(*d_my != *d_o)
						break;
				}
				return d_my+1 == c_my;
			}
			else
				return NO;
		}
	}
	return NO;
}

- (unsigned)hash {
	unsigned h =  [_peer hash];
	char *c = (void *)[[self filenameData] bytes];
	char *end = c + [[self filenameData] length];
	unsigned f = h;
	unsigned count = 100;
	while(--end >= c && --count > 0) {
		f ^= *end;
		unsigned g = f>>24;
		g |= f<<8;
		f = g;
	}
	return f;
}

- (NSComparisonResult)compare:(SharePeer *)other {
	NSComparisonResult r = [[self peer] compare:[other peer]];
	return r==NSOrderedSame?
		[[self filenameShared] compare:[other filenameShared]]
		:r;
}

- (NSString *)description {
	return [NSString stringWithFormat:@"%@:> %@", [self peer],[self filenameShared]];
}

- (NSString *)filenameShared {
	if(_filenameShared == nil) {
		@synchronized(self) { 
			if(_filenameShared==nil)
				_filenameShared = [[Message stringFromData:_filenameData] retain];
		}
	}
	return _filenameShared;
}

- (NSDictionary *)attributes { return _attributes; }

NSMutableDictionary *iconsExtension=nil;

+ (void)initialize {
	if(iconsExtension == nil)
		iconsExtension = [[NSMutableDictionary alloc] init];
}

+ (NSImage *)iconForPath:(NSString *)path {
	NSString *extension = [path pathExtension];
	NSImage *img = [iconsExtension objectForKey:extension];
	if(img == nil) {
		@synchronized(iconsExtension) {
			img = [iconsExtension objectForKey:extension];
			if(img == nil) {
				img = [[NSWorkspace sharedWorkspace] iconForFileType:extension];
				[iconsExtension setObject:img forKey:extension];
			}
		}
	}
	return img;
}

- (void)replaceIcon {
	[self willChangeValueForKey:@"icon"];
	[_icon release];
	_icon = [[SharePeer iconForPath:[self filenameShared]] retain];
	[self didChangeValueForKey:@"icon"];
}

- (NSImage *)icon { 
	if(_icon==nil) {
		@synchronized(self) {
			if(_icon==nil) {
				[self replaceIcon];
			}
		}
	}
	return _icon;
}

- (void)setSize:(unsigned long long)size {
	[self willChangeValueForKey:@"attributes"];
	[self willChangeValueForKey:@"size"];
	NSMutableDictionary *d = [[NSMutableDictionary alloc] initWithDictionary:_attributes];
	[d setObject:[NSNumber numberWithUnsignedLongLong:size] forKey:NSFileSize];
	[_attributes release];
	_attributes = d;
	[self didChangeValueForKey:@"size"];
	[self didChangeValueForKey:@"attributes"];
}

- (unsigned long long)size {
	return [[[self attributes] objectForKey:NSFileSize] unsignedLongLongValue];
}

- (Peer *)peer { return _peer; }
- (NSData *)filenameData { return _filenameData; }

+ (int)locationLastPathSeparatorOfPath:(NSData *)pathdata length:(unsigned)length {
	NSAssert2([pathdata length] >= length,@"Length %d exceeds data length %d",length,[pathdata length]);
	// c:\fun\blah.mp3 will return 6
	// c:\fun\ will return 6.  <-- error?
	// \ will return 0
	// c:\blah.mp3 will return 2
	// blah.mp3 will return -1
	if(length == 0)
		return -1;
	else {
		char *bytes = (char *)[pathdata bytes];
		char *b2 = bytes+length;
		while(--b2 >= bytes) {
			if(*b2 == '\\') {
				break;
			}
		}
		return b2-bytes;
	}
}

+ (int)locationLastPathSeparatorOfPath:(NSData *)pathdata {
	return [self locationLastPathSeparatorOfPath:pathdata length:[pathdata length]];
}

+ (int)locationFirstNonSeparatorOfPath:(NSData *)pathdata {
	char *bytes = (char *)[pathdata bytes];
	char *b2 = bytes + [pathdata length];
	while(--b2 >= bytes) {
		if(*b2 != '\\')
			break;
	}
	return b2-bytes;
}


+ (NSData *)dataLastPathComponentOfPath:(NSData *)pathdata {
	// c:\fun\blah.mp3 will return blah.mp3
	// \ will return \
	// c:\fun\ will return fun
	// c:\blah.mp3 will return blah.mp3
	// blah.mp3 will return blah.mp3
	if([pathdata length]==0)
		return pathdata;
	else {
		int end = [self locationFirstNonSeparatorOfPath:pathdata]+1;
		int separator = [self locationLastPathSeparatorOfPath:pathdata length:end];
		if(separator > 0 && ++separator < end) {		
			return [pathdata subdataWithRange:NSMakeRange(separator,end-separator)];
		}
		else
			return [pathdata subdataWithRange:NSMakeRange(0,end)];
	}
}

- (NSData *)dataLastPathComponent {
	return [SharePeer dataLastPathComponentOfPath:_filenameData];
}

+ (NSData *)dataDeletingLastPathComponentOfPath:(NSData *)pathdata {
	// c:\fun\blah.mp3 will return c:\fun
	// \ will return \
	// c:\fun\ will return c:
	// c:\blah.mp3 will return c:
	// blah.mp3 will return ""
	// c:\ will return c:
	if([pathdata length]==0)
		return pathdata;
	else {
		int end = [self locationFirstNonSeparatorOfPath:pathdata];
		if(end <= 0)
			end = 1;
		int separator = [self locationLastPathSeparatorOfPath:pathdata length:end];
		if(separator == 0)
			separator = 1;
		if(separator > 0) {
			return [pathdata subdataWithRange:NSMakeRange(0,separator)];
		}
		else
			return [NSData data];
	}
}	

- (NSData *)dataDeletingLastPathComponent {
	return [SharePeer dataDeletingLastPathComponentOfPath:_filenameData];
}
@end


@implementation ShareTree

- (id)initWithCoder:(NSCoder *)coder {
	if([coder allowsKeyedCoding]) {
		NSData *path = [coder decodeObjectForKey:@"path"];
		self = [self initWithPathData:path];
		NSArray *a = [coder decodeObjectForKey:@"shares"];
		NSEnumerator *e = [a objectEnumerator];
		SharePeer *sp;
		while(sp = [e nextObject])
			[self addShare:sp];
		return self;
	}
	else {
		[NSException raise:NSInvalidArchiveOperationException format:@"Only supports NSKeyedArchiver coders"];
	}
}


- (void)encodeWithCoder:(NSCoder *)coder {
	if([coder allowsKeyedCoding]) {
		[coder encodeObject:_path forKey:@"path"];
		NSArray *a = [self sharesThisAndSubLevels];
		[coder encodeObject:a forKey:@"shares"];
	}
	else {
		[NSException raise:NSInvalidArchiveOperationException format:@"Only supports NSKeyedArchiver coders"];
	}
}

- (id)initWithPathData:(NSData *)p {
	if(self = [super init]) {
		_subtrees = [[NSMutableArray alloc] init];
		_shares = [[NSMutableArray alloc] init];
		_path = [p retain];
		if([_path length] > 0) {
			unsigned end = [SharePeer locationFirstNonSeparatorOfPath:_path];
			startLastComponent = 0;
			if(end > 0) {
				end = [SharePeer locationLastPathSeparatorOfPath:_path length:end];
				if(end > 0 && ++end < [_path length])
					startLastComponent = end;
			}
		}
	}
	return self;
}

- (void)dealloc { 
	[_subtrees dealloc];
	_subtrees = nil;
	[_shares dealloc];
	_shares = nil;
	[_path release];
	_path = nil;
	[super dealloc];
}

- (BOOL)addShare:(Share *)share deletedLastPathComponent:(NSData *)sharepath {
	int len_sharep = [sharepath length];
	int len_myp = [_path length];
	if(len_sharep < len_myp) { //then they don't match and _path isn't a substring of sharepath
		return NO;
	}
	else { //check to see if they match.
		int len = len_sharep>len_myp?len_myp:len_sharep; //pick the shorter of the two.
		char *c_sharep = (char *)[sharepath bytes];
		char *c_myp = (char *)[_path bytes];
		char *c_s = c_sharep+len;
		char *c_m = c_myp+len;
		c_sharep += startLastComponent;
		c_myp += startLastComponent;
		BOOL ret = NO;
		while(--c_s >= c_sharep) {	//go through the whole string (backwards).
			--c_m;
			if(*c_s != *c_m) {	//make sure each byte equals the other.
				break; //stop if there is a difference.
			}
		}
		ret = c_s+1==c_sharep;	//if we got through the whole string, they are equal.
		
		if(ret==NO) {	//the paths don't match and aren't ancestors/descendants.
			return NO;
		}
		else if(len_sharep == len_myp) {	//if they match and are the same length
											//we're in the right spot.
			[share retain];	//store the share.
			[_shares removeObject:share];
			[_shares addObject:share];
			[share release];
			return YES;
		}
		else if(len_myp < len_sharep) {	//if they match and _path is shorter
												//_path is a root path of sharepath.
			//so add this share to a subtree.
			
			//first check existing subtrees.
			BOOL added = NO;
			NSEnumerator *e = [_subtrees objectEnumerator];
			ShareTree *st;
			while(st = [e nextObject]) {
				if([st addShare:share deletedLastPathComponent:sharepath]) { //then we're done.
					return YES;
				}
			}

			{ //stupid mac os X compiler.
				char *d;
				char *d_finish;
				char *c_addcomponent;
				//we have to create a new subtree.

				//find the next deepest path:
				//ie: _path = c:\windows and sharepath = c:\windows\system32\drivers\ugly.sys
				//then the next deepest would be c:\windows\system32
				
				int start = len_myp;
				int extramax = len_sharep - start;
				
				//get the characters in sharepath starting from the end of _path.
				c_addcomponent = (char *)[sharepath bytes];
				c_addcomponent+=start;
				
				//find the next component, stopping at the end or a backslash
				d_finish = c_addcomponent + extramax;
				d = c_addcomponent;
				while(++d < d_finish) {	//++d skips the first backslash
					if(*d == '\\')	//stop when we hit a backslash.
						break;
				}
				
				int extra = d - c_addcomponent;
				NSData *nextpath =  [sharepath subdataWithRange:NSMakeRange(0,len_myp + extra)]; 
				st = [[ShareTree alloc] initWithPathData:nextpath];
				[_subtrees addObject:st];
				[st release];			
				[st addShare:share deletedLastPathComponent:sharepath];
				return YES;
			}
		}
	}
}

- (BOOL)addSharePeer:(SharePeer *)share {
	NSData *sharepath = [(SharePeer *)share dataDeletingLastPathComponent];
	return [self addShare:share deletedLastPathComponent:sharepath];
}

- (BOOL)addShareLocal:(ShareLocal *)share {
	const char *utf8 =  [[(ShareLocal *)share filenameShared] UTF8String];
	NSData *sharepath = [NSData dataWithBytes:utf8 length:strlen(utf8)];
	sharepath = [SharePeer dataDeletingLastPathComponentOfPath:sharepath];
	return [self addShare:share deletedLastPathComponent:sharepath];
}

- (BOOL)addShare:(Share *)share {
	//base case: paths match.  add share to shares list. return yes.
	//share path is a descendent of self path.
	//		add to correct subtree
	//		check and see if share path matches any current subtree's paths. return to that one
	//		create new subtree.
	//return no.
	NSData *sharepath;
	BOOL r;
	if([share isKindOfClass:[SharePeer self]])
		r = [self addSharePeer:(SharePeer *)share];
	else 
		r = [self addShareLocal:(ShareLocal *)share];
	return r;
}


- (void)print { 
	NSString *str = [[NSString alloc] initWithData:_path encoding:[NSString defaultCStringEncoding]];
	NSLog(@"ShareTree at: %@",str);
	[str release];
	NSLog(@"%d shares:",[_shares count]);
	NSEnumerator *e = [_shares objectEnumerator];
	SharePeer *sp;
	while(sp = [e nextObject]) 
		NSLog(@"\t%@,",[[sp filenameSharedUnix] lastPathComponent]);
	NSLog(@"%d subtrees:",[_subtrees count]);
	ShareTree *st;
	e = [_subtrees objectEnumerator];
	while(st = [e nextObject])	
		[st print];
}

- (unsigned)countSharesThisLevel { return [_shares count]; }
- (unsigned)countSharesThisAndSubLevels { 
	unsigned c = [self countSharesThisLevel];
	NSEnumerator *e = [_subtrees objectEnumerator];
	ShareTree *st;
	while(st = [e nextObject]) {
		c+=[st countSharesThisAndSubLevels];
	}
	return c;
}

- (BOOL)isFilledThisLevel {
	return [[self subtrees] count]>0;
}

- (unsigned)countTreesFilledThisAndSubLevels {
	NSEnumerator *e = [[self subtrees] objectEnumerator];
	ShareTree *st;
	
	unsigned total = [self isFilledThisLevel]?1:0;
	
	while(st = [e nextObject]) {
		total += [st countTreesFilledThisAndSubLevels];
	}
	return total;
}	

- (NSString *)pathComponentLast {
	NSString *s = [[[NSString alloc] initWithData:[SharePeer dataLastPathComponentOfPath:_path] encoding:[NSString defaultCStringEncoding]] autorelease];
	return s;
}

- (NSString *)path {
	return [[[NSString alloc] initWithData:_path encoding:[NSString defaultCStringEncoding]] autorelease];
}


- (NSArray *)subtrees { return _subtrees; }

- (NSArray *)sharesThisLevel {
	return _shares;
}

- (NSArray *)sharesThisAndSubLevels {
	NSMutableArray *ma = [NSMutableArray arrayWithCapacity:[self countSharesThisAndSubLevels]];
	[ma addObjectsFromArray:[self sharesThisLevel]];
	NSEnumerator *e = [_subtrees objectEnumerator];
	ShareTree *st;
	while(st = [e nextObject]) {
		[ma addObjectsFromArray:[st sharesThisAndSubLevels]];
	}
	return ma;
}

@end